﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Specialized;
using System.Diagnostics;

namespace OAuthTools
{
    /// <summary>
    /// Based on OAuth 1.0 http://oauth.net/core/1.0/
    /// </summary>
    public partial class OAuth
    {

        /// <summary>
        /// A nonce is a random string, uniquely generated for each request. 
        /// The nonce allows the Service Provider to verify that a request has never been made before 
        /// and helps prevent replay attacks when requests are made over a non-secure channel (such as HTTP).
        /// </summary>
        /// <returns></returns>
        private static string GetNonce()
        {
            return new Random().Next(Int16.MinValue, Int16.MaxValue).ToString("X");
        }


        /// <summary>
        /// Unless otherwise specified by the Service Provider, the timestamp is expressed in the number 
        /// of seconds since January 1, 1970 00:00:00 GMT. The timestamp value MUST be a positive integer 
        /// and MUST be equal or greater than the timestamp used in previous requests. 
        /// </summary>
        /// <returns></returns>
        private static string GetTimeStamp()
        {
            return ((long)(DateTime.UtcNow - (new DateTime(1970, 1, 1))).TotalSeconds).ToString();
        }

        /// <summary>
        /// The Signature Base String includes the request absolute URL, tying the signature to a specific 
        /// endpoint. The URL used in the Signature Base String MUST include the scheme, authority, and path, 
        /// and MUST exclude the query.
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string NormalizeUrl(string url)
        {
            var uri = new Uri(url);
            var port = string.Empty;

            if (uri.Scheme == "http" && uri.Port != 80 ||
                uri.Scheme == "https" && uri.Port != 443 ||
                uri.Scheme == "ftp" && uri.Port != 20)
                port = string.Format(":{0}", uri.Port);

            return string.Format("{0}://{1}{2}{3}", uri.Scheme, uri.Host, port, uri.AbsolutePath);
        }

        private static NameValueCollection NormalizeCharacteres(NameValueCollection values)
        {
            var normalizeValues = new NameValueCollection();

            foreach (var key in values.AllKeys)
            {
                values[key] = Uri.EscapeDataString(values[key]);

                var result = values[key].Replace("+", UnicodeCharacter.Blank)
                                        .Replace("!", UnicodeCharacter.Exclamation)
                                        .Replace("$", UnicodeCharacter.Dollar)
                                        .Replace("'", UnicodeCharacter.Apostrophe)
                                        .Replace("(", UnicodeCharacter.LeftBracket)
                                        .Replace(")", UnicodeCharacter.RightBracket)
                                        .Replace("*", UnicodeCharacter.Asterisk);

                normalizeValues.Add(key, result);
            }

            return normalizeValues;

        }

        /// <summary>
        ///  The request parameters are collected, sorted and concatenated into a normalized string:
        ///  * Parameters in the OAuth HTTP Authorization header (Authorization Header) excluding the realm parameter.
        ///  * Parameters in the HTTP POST request body (with a content-type of application/x-www-form-urlencoded).
        ///  * HTTP GET parameters added to the URLs in the query part.
        ///  The oauth_signature parameter MUST be excluded.
        /// </summary>
        /// <returns></returns>
        private static string NormalizeParameters(Dictionary<string, string> parameters)
        {
            var normalizedParameters = new StringBuilder();
            var sortedParameters = from ps in parameters
                                   orderby ps.Key, ps.Value
                                   select ps;

            foreach (var parameter in sortedParameters)
            {
                if (normalizedParameters.Length > 0)
                    normalizedParameters.Append("&");
                normalizedParameters.AppendFormat("{0}={1}", parameter.Key, parameter.Value);
            }

            return normalizedParameters.ToString();
        }

        /// <summary>
        /// The Signature Base String is a consistent reproducible concatenation of the request elements into a single string. 
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="oAuthParameters"></param>
        /// <returns></returns>
        private static string GetSignatureBase(string httpMethod, string url, NameValueCollection oAuthParameters)
        {
            var parameters = new Dictionary<string, string>();
            foreach (var key in oAuthParameters.AllKeys)
                parameters.Add(key, oAuthParameters[key]);

            var normalizedParameters = NormalizeParameters(parameters);

            return string.Format("{0}&{1}&{2}", httpMethod, Uri.EscapeDataString(url), Uri.EscapeDataString(normalizedParameters));
        }

        /// <summary>
        /// oauth_signature is set to the concatenated encoded values of the Consumer Secret and Token Secret, separated by a ‘&’
        /// character (ASCII code 38), even if either secret is empty. The result MUST be encoded again. 
        /// </summary>
        /// <param name="consumerSecret"></param>
        /// <param name="signatureBase"></param>
        /// <param name="tokenSecret"></param>
        /// <returns></returns>
        private static string GetSignature(string consumerSecret, string signatureBase, string tokenSecret = null)
        {
            var hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(string.Concat(consumerSecret, "&", tokenSecret)));

            var data = Encoding.ASCII.GetBytes(signatureBase);
            var hashData = hmacsha1.ComputeHash(data);

            return Uri.EscapeDataString(Convert.ToBase64String(hashData));
        }


        private NameValueCollection GetOAuthParameters(string httpMethod, string url, NameValueCollection query = null, string token = null, string tokenSecret = null, string verifier = null, string callback = null)
        {
            var oAuthParameters = new NameValueCollection
                                        {
                                            {"oauth_timestamp",GetTimeStamp()},    
                                            {"oauth_nonce",GetNonce()},
                                            {"oauth_version", "1.0"},
                                            {"oauth_signature_method", "HMAC-SHA1"},      
                                            {"oauth_consumer_key", _consumerKey},                                            
                                        };

            if (!String.IsNullOrEmpty(token)) oAuthParameters.Add("oauth_token", token);

            if (!String.IsNullOrEmpty(verifier)) oAuthParameters.Add("oauth_verifier", verifier);

            if (!String.IsNullOrEmpty(callback)) oAuthParameters.Add("oauth_callback", callback);

            if (query == null) query = new NameValueCollection();

            foreach (var oauthKey in oAuthParameters.AllKeys)
                query.Add(oauthKey, oAuthParameters[oauthKey]);

            var signatureBase = GetSignatureBase(httpMethod, NormalizeUrl(url), NormalizeCharacteres(query));
            var signature = GetSignature(_consumerSecret, signatureBase, tokenSecret);
            oAuthParameters.Add("oauth_signature", signature);

            Debug.WriteLine("oauth_signature {0}", signature);

            return oAuthParameters;
        }
    }
}
